require File.dirname(__FILE__)+"/../test_helper"
require "starframe/sprite"

class StarFrame::Sprite
  attr_reader :options, :texture
end

class TestStarFrameSprite < Test::Unit::TestCase
  class NoTextureSprite < StarFrame::Sprite
  end
  class TexturedSprite < StarFrame::Sprite
    @texture = StarRuby::Texture.load("texture")
  end
  class TexturedSubSprite < TexturedSprite
    @texture = StarRuby::Texture.load("texture_sub")
  end
  class TexturedSubNotChangedTextureSprite < TexturedSprite
  end
  
  class CenteringTexturedSprite < StarFrame::Sprite
    @texture  = StarRuby::Texture.load("options_texture")
    @center_x = 2
    @center_y = 4
  end
  class NegativeCenteringTexturedSprite < StarFrame::Sprite
    @texture  = StarRuby::Texture.load("options_texture")
    @center_x = -2
    @center_y = -4
  end
  
  class MovableSprite < StarFrame::Sprite
    @texture = StarRuby::Texture.load("movable")
    def init options
      @movement_x = 10
      @movement_y = 12
      @angular_velocity = 14
    end
  end
  
  class TestSprite < StarFrame::Sprite
    @texture = StarRuby::Texture.load("test")
  end
  class TallSprite < StarFrame::Sprite
    @texture = StarRuby::Texture.new(10, 20)
  end
  class FatSprite < StarFrame::Sprite
    @texture = StarRuby::Texture.new(20, 10)
  end
  
  def setup
    @sprite = TestSprite.new
    
    @screen = StarRuby::Texture.new(100, 100)
  end
  
  def test_notexture_sprite
    assert_nil NoTextureSprite.texture
    assert_equal 0,      NoTextureSprite.center_x
    assert_equal 0,      NoTextureSprite.center_y
    assert_equal [0, 0], NoTextureSprite.center
  end
  def test_textured_sprite
    assert_kind_of StarRuby::Texture, TexturedSprite.texture
    assert_equal "texture",           TexturedSprite.texture.path
    assert_equal 0,      TexturedSprite.center_x
    assert_equal 0,      TexturedSprite.center_y
    assert_equal [0, 0], TexturedSprite.center
    assert_not_equal TexturedSubSprite.texture,       TexturedSprite.texture
    assert_not_equal CenteringTexturedSprite.texture, TexturedSprite.texture
    assert_not_equal TestSprite.texture,              TexturedSprite.texture
  end
  def test_textured_sprite_subclass
    assert_kind_of StarRuby::Texture, TexturedSubSprite.texture
    assert_equal "texture_sub",       TexturedSubSprite.texture.path
    assert_equal 0,      TexturedSubSprite.center_x
    assert_equal 0,      TexturedSubSprite.center_y
    assert_equal [0, 0], TexturedSubSprite.center
    assert_not_equal TexturedSprite.texture,          TexturedSubSprite.texture
    assert_not_equal CenteringTexturedSprite.texture, TexturedSubSprite.texture
    assert_not_equal TestSprite.texture,              TexturedSubSprite.texture
  end
  def test_textured_sprite_subclass_not_changed_texture
    assert_kind_of StarRuby::Texture, TexturedSubNotChangedTextureSprite.texture
    assert_equal "texture",           TexturedSubNotChangedTextureSprite.texture.path
    assert_equal 0,      TexturedSubSprite.center_x
    assert_equal 0,      TexturedSubSprite.center_y
    assert_equal [0, 0], TexturedSubSprite.center
    assert_equal     TexturedSprite.texture,          TexturedSubNotChangedTextureSprite.texture
    assert_not_equal CenteringTexturedSprite.texture, TexturedSubNotChangedTextureSprite.texture
    assert_not_equal TestSprite.texture,              TexturedSubNotChangedTextureSprite.texture
  end
  def test_centering_textured_sprite
    assert_kind_of StarRuby::Texture, CenteringTexturedSprite.texture
    assert_equal "options_texture",   CenteringTexturedSprite.texture.path
    assert_equal 2,      CenteringTexturedSprite.center_x
    assert_equal 4,      CenteringTexturedSprite.center_y
    assert_equal [2, 4], CenteringTexturedSprite.center
    assert_not_equal TexturedSprite.texture,    CenteringTexturedSprite.texture
    assert_not_equal TexturedSubSprite.texture, CenteringTexturedSprite.texture
    assert_not_equal TestSprite.texture,        CenteringTexturedSprite.texture
  end
  
  def test_notexture_sprite_instance
    sprite = NoTextureSprite.new
    assert_equal NoTextureSprite.texture,  sprite.texture
    assert_equal NoTextureSprite.center_x, sprite.center_x
    assert_equal NoTextureSprite.center_y, sprite.center_y
    assert_equal NoTextureSprite.center,   sprite.center
    assert_equal({}, sprite.options)
  end
  def test_textured_sprite_instance
    sprite = TexturedSprite.new
    assert_equal TexturedSprite.texture,  sprite.texture
    assert_equal TexturedSprite.center_x, sprite.center_x
    assert_equal TexturedSprite.center_y, sprite.center_y
    assert_equal TexturedSprite.center,   sprite.center
    assert_equal({}, sprite.options)
  end
  def test_textured_sprite_subclass_instance
    sprite = TexturedSubSprite.new
    assert_equal TexturedSubSprite.texture,  sprite.texture
    assert_equal TexturedSubSprite.center_x, sprite.center_x
    assert_equal TexturedSubSprite.center_y, sprite.center_y
    assert_equal TexturedSubSprite.center,   sprite.center
    assert_equal({}, sprite.options)
  end
  def test_centering_textured_sprite_instance
    sprite = CenteringTexturedSprite.new
    assert_equal CenteringTexturedSprite.texture,  sprite.texture
    assert_equal CenteringTexturedSprite.center_x, sprite.center_x
    assert_equal CenteringTexturedSprite.center_y, sprite.center_y
    assert_equal CenteringTexturedSprite.center,   sprite.center
    assert_equal({:center_x => 2, :center_y => 4}, sprite.options)
  end
  
  def test_initialize
    sprite = TestSprite.new
    assert_equal 0,    sprite.x
    assert_equal 0,    sprite.y
    assert_equal 0,    sprite.angle
    assert_equal 0xFF, sprite.alpha
  end
  def test_initialize_with_position
    sprite = TestSprite.new(1, 2)
    assert_equal 1,    sprite.x
    assert_equal 2,    sprite.y
    assert_equal 0,    sprite.angle
    assert_equal 0xFF, sprite.alpha
  end
  def test_initialize_with_options
    sprite = TestSprite.new(1, 2, :angle => 3, :alpha => 4)
    assert_equal 1, sprite.x
    assert_equal 2, sprite.y
    assert_equal 3, sprite.angle
    assert_equal 4, sprite.alpha
  end
  
  def test_positions
    sprite = MovableSprite.new(1, 2)
    assert_kind_of Array, sprite.position
    assert_equal [1, 2],  sprite.position
    assert_equal 1, sprite.x
    assert_equal 2, sprite.y
  end
  def test_movement
    sprite = MovableSprite.new
    assert_kind_of Array,  sprite.movement
    assert_equal [10, 12], sprite.movement
    assert_equal 10, sprite.movement_x
    assert_equal 12, sprite.movement_y
    assert_equal 14, sprite.angular_velocity
  end
  def test_movement_default
    sprite = TestSprite.new
    assert_equal 0, sprite.movement_x
    assert_equal 0, sprite.movement_y
    assert_equal 0, sprite.angular_velocity
  end
  
  def test_render_options
    {
      :alpha => 0xFF,
      :angle => 0,
      :tone_red => 0,
      :tone_green => 0,
      :tone_blue => 0,
      :src_x => 0,
      :src_y => 0,
      :src_height => 1,
      :src_width  => 1,
      :scale_x => 1,
      :scale_y => 1,
      :saturation => 0xFF,
      :blend_type => ":alpha",
    }.each do |key, default|
      assert_equal eval("#{default}"), eval("@sprite.#{key}")
      assert_equal nil,                @sprite.options[key]
      assert_equal 10,                 eval("@sprite.#{key} = 10")
      assert_equal 10,                 eval("@sprite.#{key}")
      assert_equal 10,                 @sprite.options[key]
      assert_equal eval("#{default}"), eval("@sprite.#{key} = #{default}")
      assert_equal eval("#{default}"), eval("@sprite.#{key}")
      assert_equal nil,                @sprite.options[key]
    end
  end
  def test_tone
    @sprite.tone_red   = 1
    @sprite.tone_green = 2
    @sprite.tone_blue  = 3
    
    @sprite.tone = 4
    assert_equal 4, @sprite.tone_red
    assert_equal 4, @sprite.tone_green
    assert_equal 4, @sprite.tone_blue
    assert_equal 4, @sprite.options[:tone_red]
    assert_equal 4, @sprite.options[:tone_green]
    assert_equal 4, @sprite.options[:tone_blue]
    
    @sprite.tone = 0
    assert_equal 0, @sprite.tone_red
    assert_equal 0, @sprite.tone_green
    assert_equal 0, @sprite.tone_blue
    assert_equal nil, @sprite.options[:tone_red]
    assert_equal nil, @sprite.options[:tone_green]
    assert_equal nil, @sprite.options[:tone_blue]
  end
  def test_scale
    @sprite.scale_x = 2
    @sprite.scale_y = 3
    
    @sprite.scale = 4
    assert_equal 4, @sprite.scale_x
    assert_equal 4, @sprite.scale_y
    assert_equal 4, @sprite.options[:scale_x]
    assert_equal 4, @sprite.options[:scale_y]
    
    @sprite.scale = 1
    assert_equal 1, @sprite.scale_x
    assert_equal 1, @sprite.scale_y
    assert_equal nil, @sprite.options[:scale_x]
    assert_equal nil, @sprite.options[:scale_y]
  end
  
  def test_scale_width_to
    sprite = TallSprite.new
    
    sprite.scale_width_to 30
    assert_equal 3, sprite.scale_x
    assert_equal 1, sprite.scale_y
    
    sprite.scale_width_to 2
    assert_equal 0.2, sprite.scale_x
    assert_equal 1,   sprite.scale_y
  end
  def test_scale_height_to
    sprite = TallSprite.new
    
    sprite.scale_height_to 30
    assert_equal 1,   sprite.scale_x
    assert_equal 1.5, sprite.scale_y
    
    sprite.scale_height_to 2
    assert_equal 1,   sprite.scale_x
    assert_equal 0.1, sprite.scale_y
  end
  def test_scale_to_fit_on
    [
      [TallSprite, 2],
      [FatSprite,  1.5],
    ].each do |sprite_class, scale|
      sprite = sprite_class.new
      sprite.scale_to_fit_on 30
      assert_equal 1.5, sprite.scale_x
      assert_equal 1.5, sprite.scale_y
      
      sprite.scale_to_fit_on 30, 40
      assert_equal scale, sprite.scale_x
      assert_equal scale, sprite.scale_y
    end
  end
  def test_scale_to_crop_on
    [
      [TallSprite, 3],
      [FatSprite,  4],
    ].each do |sprite_class, scale|
      sprite = sprite_class.new
      sprite.scale_to_crop_on 30
      assert_equal 3, sprite.scale_x
      assert_equal 3, sprite.scale_y
      
      sprite.scale_to_crop_on 30, 40
      assert_equal scale, sprite.scale_x
      assert_equal scale, sprite.scale_y
    end
  end
  
  def test_to_texture
    assert_equal @sprite.texture, @sprite.to_texture
  end
  
  def test_render_to
    @sprite.x = 0
    @sprite.y = 1
    @sprite.render_to @screen
    
    assert_equal :render_texture, @screen.last_method
    assert_equal @sprite.texture, @screen.last_method_args[0]
    assert_equal 0, @screen.last_method_args[1]
    assert_equal 1, @screen.last_method_args[2]
    assert_equal 0, @screen.last_method_args[3].size
    assert_equal({}, @screen.last_method_args[3])
  end
  def test_render_to_options
    @sprite.x = 0
    @sprite.y = 1
    @sprite.alpha = 2
    @sprite.angle = 3
    @sprite.src_x = 4
    @sprite.src_y = 5
    @sprite.render_to @screen
    
    assert_equal :render_texture, @screen.last_method
    assert_equal @sprite.texture, @screen.last_method_args[0]
    assert_equal 0, @screen.last_method_args[1]
    assert_equal 1, @screen.last_method_args[2]
    assert_equal 4, @screen.last_method_args[3].size
    assert_equal 2, @screen.last_method_args[3][:alpha]
    assert_equal 3, @screen.last_method_args[3][:angle]
    assert_equal 4, @screen.last_method_args[3][:src_x]
    assert_equal 5, @screen.last_method_args[3][:src_y]
  end
  def test_render_to_centering_texture
    sprite = CenteringTexturedSprite.new
    sprite.render_to @screen
    assert_equal :render_texture, @screen.last_method
    assert_equal sprite.texture,  @screen.last_method_args[0]
    assert_equal -2,      @screen.last_method_args[1]
    assert_equal -4,      @screen.last_method_args[2]
    assert_equal 2,       @screen.last_method_args[3].size
    assert_equal 2,       @screen.last_method_args[3][:center_x]
    assert_equal 4,       @screen.last_method_args[3][:center_y]
  end
  def test_render_to_negative_centering_texture
    sprite = NegativeCenteringTexturedSprite.new
    sprite.render_to @screen
    assert_equal :render_texture, @screen.last_method
    assert_equal sprite.texture,  @screen.last_method_args[0]
    assert_equal 2,       @screen.last_method_args[1]
    assert_equal 4,       @screen.last_method_args[2]
    assert_equal 2,       @screen.last_method_args[3].size
    assert_equal -2,      @screen.last_method_args[3][:center_x]
    assert_equal -4,      @screen.last_method_args[3][:center_y]
  end
  
  
  def test_call
    sprite = MovableSprite.new(1, 2)
    sprite.call
    assert_equal [11, 14],  sprite.position
    assert_equal 14, sprite.angle
    assert_equal 14, sprite.options[:angle]
  end
  def test_move
    sprite = MovableSprite.new(1, 2)
    sprite.move
    assert_equal [11, 14],  sprite.position
    assert_equal 14, sprite.angle
    assert_equal 14, sprite.options[:angle]
  end
  def test_move_minute
    sprite = MovableSprite.new(1, 2)
    sprite.move_minute 0.6
    assert_equal [7, 9.2],  sprite.position
    assert_equal 8.4, sprite.angle
    assert_equal 8.4, sprite.options[:angle]
  end
  def test_futures
    sprite = MovableSprite.new(1, 2)
    assert_equal [7, 9.2],  sprite.future_position(0.6)
    assert_equal 8.4, sprite.future_angle(0.6)
  end
  
  
  def test_squared_distance
    sprite1 = TestSprite.new(0, 0)
    sprite2 = TestSprite.new(3, 4)
    assert_equal 25, sprite1.squared_distance(sprite2)
    
    sprite1 = TestSprite.new(0, 0)
    sprite2 = TestSprite.new(2, 2)
    assert_equal 8, sprite1.squared_distance(sprite2)
  end
  def test_distance
    sprite1 = TestSprite.new(0, 0)
    sprite2 = TestSprite.new(3, 4)
    assert_equal 5, sprite1.distance(sprite2)
    
    sprite1 = TestSprite.new(0, 0)
    sprite2 = TestSprite.new(2, 2)
    assert_in_delta 2.828, 0.00, sprite1.distance(sprite2)
  end
  
  def bench
    require "benchmark"
    sprite1 = TestSprite.new(0, 0)
    sprite2 = TestSprite.new(2, 2)
    Benchmark.bm do |bm|
      bm.report{ 1000.times{ sprite1.distance(sprite2) } }
      bm.report{ 1000.times{ sprite1.distance2(sprite2) } }
    end
  end
end
